﻿namespace JavaScript8000.Parser
{
    using System;
    using System.Collections.Generic;

    using JavaScript8000.Common;
    using JavaScript8000.Scaner;

    /// <summary>
    /// Class which parse tokens into tree.
    /// </summary>
    public static class Parser
    {
        /// <summary>
        /// The result of scan operation.
        /// </summary>
        private static ScanResults scanResults;

        /// <summary>
        /// Index of current token.
        /// </summary>
        private static int currentToken;

        /// <summary>
        /// Initializes a parser and starts parse.
        /// </summary>
        /// <param name="scResults">The scan results.</param>
        /// <returns>Parser result.</returns>
        public static ParseResult Parse(ScanResults scResults)
        {
            if (scResults.Errors.Count != 0)
            {
                throw new InvalidOperationException("Attempt to parse code with lexical errors.");
            }

            scanResults = scResults;
            currentToken = 0;

            ParseResult result = new ParseResult();
            result.GrammarTree = new GrammarNode("Program");

            RuleZoneError error;

            if (CheckRule("Program", result.GrammarTree, out error))
            {
                result.WasError = true;
                result.ErrorMessage = error != null ? error.Message : string.Empty;
            }
            else if (currentToken < scanResults.Tokens.Count - 1)
            {
                result.WasError = true;
                result.ErrorMessage = "Superfluous tokens are detected.";
            }

            return result;
        }

        /// <summary>
        /// Checks the rule.
        /// </summary>
        /// <param name="ruleName">Name of the rule.</param>
        /// <param name="parentNode">The parent node.</param>
        /// <param name="error">The error.</param>
        /// <returns>
        /// True if was error.
        /// </returns>
        private static bool CheckRule(string ruleName, GrammarNode parentNode, out RuleZoneError error)
        {
            List<string> rule = Grammar.GetRule(ruleName);

            if (rule == null)
            {
                throw new ArgumentException(string.Format("A rule with name \"{0}\" isn't exist.", ruleName));
            }

            // Create stack.
            Stack<RuleZone> stack = new Stack<RuleZone>();
            Stack<string> bracketsInErrorZone = new Stack<string>();
            RuleZone currentZone = new RuleZone { Children = new List<GrammarNode>(), TokenNumber = currentToken };

            int i = 0;
            bool wasMistake = false;

            while (i < rule.Count)
            {
                switch (rule[i])
                {
                    case "{":
                        if (!wasMistake)
                        {
                            stack.Push(currentZone);
                            currentZone = new RuleZone { Children = new List<GrammarNode>(), TokenNumber = currentToken, RuleTokenNumber = i };
                        }
                        else
                        {
                            bracketsInErrorZone.Push("}");
                        }

                        break;
                    case "}":
                        if (wasMistake)
                        {
                            if (bracketsInErrorZone.Count == 0)
                            {
                                wasMistake = false;
                                RuleZone prev = stack.Pop();
                                if (prev.Error == null)
                                {
                                    prev.Error = new RuleZoneError();
                                }

                                if (prev.Error.CheckedTokenNumber <= currentZone.Error.CheckedTokenNumber)
                                {
                                    prev.Error.CheckedTokenNumber = currentZone.Error.CheckedTokenNumber;
                                    prev.Error.Children.Clear();
                                    prev.Error.Children.AddRange(prev.Children);
                                    prev.Error.Children.AddRange(currentZone.Error.Children);
                                    prev.Error.Message = currentZone.Error.Message;
                                }

                                currentToken = currentZone.TokenNumber;

                                currentZone = prev;
                            }
                            else if (rule[i] != bracketsInErrorZone.Pop())
                            {
                                throw new InvalidOperationException("Wrong brackets...");
                            }
                        }
                        else
                        {
                            // Go try next loop.
                            i = currentZone.RuleTokenNumber;
                            currentZone.TokenNumber = currentToken;
                            stack.Peek().Children.AddRange(currentZone.Children);
                            currentZone.Children.Clear();
                        }

                        break;
                    case "(":
                        if (!wasMistake)
                        {
                            stack.Push(currentZone);
                            currentZone = new RuleZone { Children = new List<GrammarNode>(), TokenNumber = currentToken };
                        }
                        else
                        {
                            bracketsInErrorZone.Push(")");
                        }

                        break;
                    case ")":
                        if (wasMistake)
                        {
                            if (bracketsInErrorZone.Count == 0)
                            {
                                RuleZone prev = stack.Pop();
                                if (prev.Error == null)
                                {
                                    prev.Error = new RuleZoneError();
                                }

                                if (prev.Error.CheckedTokenNumber <= currentZone.Error.CheckedTokenNumber)
                                {
                                    prev.Error.CheckedTokenNumber = currentZone.Error.CheckedTokenNumber;
                                    prev.Error.Children.Clear();
                                    prev.Error.Children.AddRange(prev.Children);
                                    prev.Error.Children.AddRange(currentZone.Error.Children);
                                    prev.Error.Message = currentZone.Error.Message;
                                }

                                currentToken = currentZone.TokenNumber;

                                currentZone = prev;

                            }
                            else if (rule[i] != bracketsInErrorZone.Pop())
                            {
                                throw new InvalidOperationException("Wrong brackets...");
                            }
                        }
                        else
                        {
                            RuleZone prev = stack.Pop();

                            if (prev.Error == null)
                            {
                                prev.Error = new RuleZoneError();
                            }

                            if (currentZone.Error != null && prev.Error.CheckedTokenNumber <= currentZone.Error.CheckedTokenNumber)
                            {
                                prev.Error.CheckedTokenNumber = currentZone.Error.CheckedTokenNumber;
                                prev.Error.Children.Clear();
                                prev.Error.Children.AddRange(currentZone.Error.Children);
                                prev.Error.Message = currentZone.Error.Message;
                            }

                            prev.Children.AddRange(currentZone.Children);
                            currentZone = prev;
                        }

                        break;
                    case "[":
                        if (!wasMistake)
                        {
                            stack.Push(currentZone);
                            currentZone = new RuleZone { Children = new List<GrammarNode>(), TokenNumber = currentToken };
                        }
                        else
                        {
                            bracketsInErrorZone.Push("]");
                        }

                        break;
                    case "]":
                        if (wasMistake)
                        {
                            if (bracketsInErrorZone.Count == 0)
                            {
                                wasMistake = false;
                                RuleZone prev = stack.Pop();
                                if (prev.Error == null)
                                {
                                    prev.Error = new RuleZoneError();
                                }

                                if (prev.Error.CheckedTokenNumber <= currentZone.Error.CheckedTokenNumber)
                                {
                                    prev.Error.CheckedTokenNumber = currentZone.Error.CheckedTokenNumber;
                                    prev.Error.Children.Clear();
                                    prev.Error.Children.AddRange(prev.Children);
                                    prev.Error.Children.AddRange(currentZone.Error.Children);
                                    prev.Error.Message = currentZone.Error.Message;
                                }

                                currentToken = currentZone.TokenNumber;

                                currentZone = prev;

                            }
                            else if (rule[i] != bracketsInErrorZone.Pop())
                            {
                                throw new InvalidOperationException("Wrong brackets...");
                            }
                        }
                        else
                        {
                            RuleZone prev = stack.Pop();

                            if (prev.Error == null)
                            {
                                prev.Error = new RuleZoneError();
                            }

                            if (currentZone.Error != null && prev.Error.CheckedTokenNumber <= currentZone.Error.CheckedTokenNumber)
                            {
                                prev.Error.CheckedTokenNumber = currentZone.Error.CheckedTokenNumber;
                                prev.Error.Children.Clear();
                                prev.Error.Children.AddRange(prev.Children);
                                prev.Error.Children.AddRange(currentZone.Error.Children);
                                prev.Error.Message = currentZone.Error.Message;
                            }

                            prev.Children.AddRange(currentZone.Children);
                            currentZone = prev;
                        }

                        break;
                    case "|":
                        if (wasMistake)
                        {
                            if (bracketsInErrorZone.Count == 0)
                            {
                                wasMistake = false;
                                currentZone.Children.Clear();
                                currentToken = currentZone.TokenNumber;
                            }
                        }
                        else
                        {
                            if (stack.Count > 0)
                            {
                                RuleZone prev = stack.Pop();

                                if (prev.Error == null)
                                {
                                    prev.Error = new RuleZoneError();
                                }

                                if (currentZone.Error != null && prev.Error.CheckedTokenNumber <= currentZone.Error.CheckedTokenNumber)
                                {
                                    prev.Error.CheckedTokenNumber = currentZone.Error.CheckedTokenNumber;
                                    prev.Error.Children.Clear();
                                    prev.Error.Children.AddRange(prev.Children);
                                    prev.Error.Children.AddRange(currentZone.Error.Children);
                                    prev.Error.Message = currentZone.Error.Message;
                                }

                                prev.Children.AddRange(currentZone.Children);
                                currentZone = prev;
                            }

                            // Go to the end of current zone.
                            Stack<string> additionalStack = new Stack<string>();
                            i++;
                            while (i < rule.Count)
                            {
                                if (rule[i] == "{")
                                {
                                    additionalStack.Push("}");
                                }
                                else if (rule[i] == "(")
                                {
                                    additionalStack.Push(")");
                                }
                                else if (rule[i] == "[")
                                {
                                    additionalStack.Push("]");
                                }
                                else if (rule[i] == "}" || rule[i] == ")" || rule[i] == "]")
                                {
                                    if (additionalStack.Count == 0)
                                    {
                                        break;
                                    }
                                    else if (rule[i] != additionalStack.Pop())
                                    {
                                        throw new InvalidOperationException("Wrong brackets...(2)");
                                    }
                                }

                                i++;
                            }
                        }

                        break;
                    default:
                        if (!wasMistake)
                        {
                            if (rule[i].StartsWith("\"") && rule[i].EndsWith("\""))
                            {
                                if (scanResults.Tokens.Count > currentToken && SystemTokens.GetCode(rule[i].Substring(1, rule[i].Length - 2))
                                    == scanResults.Tokens[currentToken].Code)
                                {
                                    // Match!
                                    currentZone.Children.Add(new GrammarNode(scanResults.Tokens[currentToken]));

                                    currentToken++;
                                }
                                else
                                {
                                    // Mistake!
                                    wasMistake = true;
                                    if (currentZone.Error == null)
                                    {
                                        currentZone.Error = new RuleZoneError();
                                    }

                                    if (currentZone.Error.CheckedTokenNumber <= currentToken)
                                    {
                                        currentZone.Error.CheckedTokenNumber = currentToken;
                                        currentZone.Error.Children.Clear();
                                        currentZone.Error.Children.AddRange(currentZone.Children);
                                        currentZone.Error.Children.Add(new GrammarNode(string.Format("ERROR!!! Last try was: {0}", rule[i])));
                                        currentZone.Error.Message = string.Format(
                                        "Error in \"{0}\" rule: can't find {1}", parentNode.Value, rule[i]);
                                    }
                                }
                            }
                            else if (rule[i].StartsWith("<") && rule[i].EndsWith(">"))
                            {
                                if (scanResults.Tokens.Count <= currentToken)
                                {
                                    // Mistake!
                                    wasMistake = true;
                                    if (currentZone.Error == null)
                                    {
                                        currentZone.Error = new RuleZoneError();
                                    }

                                    if (currentZone.Error.CheckedTokenNumber <= currentToken)
                                    {
                                        currentZone.Error.CheckedTokenNumber = currentToken;
                                        currentZone.Error.Children.Clear();
                                        currentZone.Error.Children.AddRange(currentZone.Children);
                                        currentZone.Error.Children.Add(new GrammarNode(string.Format("ERROR!!! Last try was: {0}", rule[i])));
                                        currentZone.Error.Message = string.Format(
                                        "Error in \"{0}\" rule: can't find {1}", parentNode.Value, rule[i]);
                                    }
                                }
                                else
                                {
                                    // Get necessary code.
                                    int code = -1;
                                    switch (rule[i].ToLower())
                                    {
                                        case "<string_literal>":
                                            code = SystemTokens.StringLiteralCode;
                                            break;
                                        case "<decimal_literal>":
                                            code = SystemTokens.NumericLiteralCode;
                                            break;
                                        case "<identifier>":
                                            code = SystemTokens.IdCode;
                                            break;
                                        case "<eof>":
                                            code = SystemTokens.EofCode;
                                            break;
                                        case "<bool_literal>":
                                            code = SystemTokens.BoolLiteralCode;
                                            break;
                                        case "<null_literal>":
                                            code = SystemTokens.NullLiteral;
                                            break;
                                    }

                                    if (code == -1)
                                    {
                                        throw new Exception("Unexpected literal O_o.");
                                    }

                                    if (scanResults.Tokens[currentToken].Code == code)
                                    {
                                        // Match!
                                        GrammarNode child = new GrammarNode(rule[i]);
                                        if (code != SystemTokens.EofCode)
                                        {
                                            child.Children.Add(new GrammarNode(scanResults.Tokens[currentToken]));
                                        }

                                        currentZone.Children.Add(child);

                                        currentToken++;
                                    }
                                    else
                                    {
                                        // Mistake!
                                        wasMistake = true;

                                        if (currentZone.Error == null)
                                        {
                                            currentZone.Error = new RuleZoneError();
                                        }

                                        if (currentZone.Error.CheckedTokenNumber <= currentToken)
                                        {
                                            currentZone.Error.CheckedTokenNumber = currentToken;
                                            currentZone.Error.Children.Clear();
                                            currentZone.Error.Children.AddRange(currentZone.Children);
                                            currentZone.Error.Children.Add(new GrammarNode(string.Format("ERROR!!! Last try was: {0}", rule[i])));
                                            currentZone.Error.Message = string.Format(
                                            "Error in \"{0}\" rule: can't find {1}", parentNode.Value, rule[i]);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // Rule.
                                GrammarNode child = new GrammarNode(rule[i]);
                                currentZone.Children.Add(child);
                                RuleZoneError error1;

                                wasMistake = CheckRule(rule[i], child, out error1);

                                if (currentZone.Error == null)
                                {
                                    currentZone.Error = new RuleZoneError();
                                }

                                if (error1 != null && currentZone.Error.CheckedTokenNumber <= error1.CheckedTokenNumber)
                                {
                                    currentZone.Error.CheckedTokenNumber = error1.CheckedTokenNumber;
                                    currentZone.Error.Children.Clear();
                                    currentZone.Error.Children.AddRange(currentZone.Children);
                                    currentZone.Error.Children.Remove(child);
                                    GrammarNode child1 = new GrammarNode(rule[i]);
                                    child1.Children.AddRange(error1.Children);
                                    currentZone.Error.Children.Add(child1);
                                    currentZone.Error.Message = error1.Message;
                                }
                            }
                        }

                        break;
                }

                // Go to next rule token.
                i++;
            }

            if (stack.Count > 0)
            {
                throw new Exception("WTF?! Stack is not empty!");
            }

            error = currentZone.Error;

            if (wasMistake)
            {
                parentNode.Children.AddRange(currentZone.Error.Children);
                return true;
            }

            parentNode.Children.AddRange(currentZone.Children);

            return false;
        }
    }
}
